package engine.motionDetection;

import java.awt.image.BufferedImage;
import java.util.Arrays;

import engine.motionDetection.modelisation.BooleanImage;
import engine.motionDetection.modelisation.CoordLabel;
import engine.motionDetection.modelisation.ImagePix;

/**
 * 
 * Motion detection of an object
 * @author HINB
 *
 */
public class ObjectTracking {
	
	private static final long serialVersionUID = 1;
	
	/* *********************************************************
	 * Attributes
	 * ******************************************************** */
	
	
	
	/* *********************************************************
	 * Static Methods
	 * ********************************************************* */
	/**
	 * Beginning of a set of treatments on the background and foreground images
	 * Update of the coordinates of the object
	 */
	public static int[] track(BufferedImage image_buf_bg, BufferedImage image_buf_fg)
	{		
		System.out.println("engine.motionDetection : track");	
		//Subtraction
		BooleanImage image_sub = 
			BooleanImage.CreateBooleanImageFromBufferedImages(
					image_buf_bg, image_buf_fg, 50);
		
		//Morphological transformation
		BooleanImage image_morph_bw = morphTransform(image_sub);
		
		//Motion test
		return motionTest(image_morph_bw);
	}
	

	
	
	/**
	 * Set of transformation of the image
	 * @param gray ImagePix to transform
	 * @return the transformed ImagePix
	 */
	public static BooleanImage morphTransform(BooleanImage gray)
	{
		System.out.println("engine.motionDetection : morphTransform");
		int width = gray.getWidth();
		int height = gray.getHeight();
		
		BooleanImage gray_morph = new BooleanImage(width, height);
		BooleanImage gray_ero = new BooleanImage(width, height);
		BooleanImage gray_dil = new BooleanImage(width, height);
		
		gray_ero = erosion(gray);
		gray_dil = dilatation(gray_ero);
		gray_morph = reconstruction(gray, gray_dil);
		
		return gray_morph;
	}
	
	 
	/**
	  * Erosion operation of an image
	  * @param gray ImagePix to erode
	  * @return the eroded ImagePix
	  */
	public static BooleanImage erosion(BooleanImage gray)
	{		
		System.out.println("engine.motionDetection : erosion");
		int width = gray.getWidth();
		int height = gray.getHeight();
		
		BooleanImage gray_ero = new BooleanImage(width, height);
		
		boolean up, down, left, right, center;
		
		for(int j=1; j<height-1; j++)
		{
			for(int i=1; i<width-1; i++)
			{			
				center = gray.getPix(i, j);
				left = gray.getPix(i-1, j);
				right = gray.getPix(i+1, j);
				up = gray.getPix(i, j-1);
				down = gray.getPix(i, j+1);
				
				//Mask
				if( center && left && right && up && down){
					gray_ero.setPix(i, j, true);
				}else{
					gray_ero.setPix(i, j, false);
				}
			}
		}
		return gray_ero;
	}
	
	
	/**
	 * Dilatation operation of an image
	 * @param gray ImagePix to dilate
	 * @return the dilated ImagePix
	 */
	public static BooleanImage dilatation(BooleanImage gray)
	{
		System.out.println("engine.motionDetection : dilatation");
		int width = gray.getWidth();
		int height = gray.getHeight();
		
		BooleanImage gray_dil = new BooleanImage(width, height);
		
		boolean up, down, left, right, center;
		
		for(int j=1; j<height-1; j++)
		{
			for(int i=1; i<width-1; i++)
			{			
				center = gray.getPix(i, j);
				left = gray.getPix(i-1, j);
				right = gray.getPix(i+1, j);
				up = gray.getPix(i, j-1);
				down = gray.getPix(i, j+1);
				
				//Mask
				if( center|| left || right || up || down ){
					gray_dil.setPix(i, j,true);
				}else{
					gray_dil.setPix(i, j, false);
				}
			}
		}
		return gray_dil;
	}
	
	
	/**
	 * Reconstruction operation of an image
	 * @param gray_thr threshold image
	 * @param gray dilated image
	 * @return the reconstructed ImagePix
	 */
	public static BooleanImage reconstruction(BooleanImage gray_thr,
			BooleanImage gray_dil)
	{
		System.out.println("engine.motionDetection : reconstruction");
		int width = gray_thr.getWidth();
		int height = gray_thr.getHeight();
		
		BooleanImage gray_reco = new BooleanImage(gray_dil);
		BooleanImage gray_aux = new BooleanImage(width, height);	
		
		boolean up, down, left, right, center, center_dil, center_aux;
		
		boolean change;
			
		do 
		{
			change = false;
			
			for(int j=1; j<height-1; j++)
			{
				for(int i=1; i<width-1; i++)
				{	
					center = gray_thr.getPix(i, j);
					left = gray_thr.getPix(i, j);
					right = gray_thr.getPix(i, j);
					up = gray_thr.getPix(i, j);
					down = gray_thr.getPix(i, j);
					
					center_dil = gray_dil.getPix(i, j);
					center_aux = gray_aux.getPix(i, j);
					
					if(center_dil && center_aux)
					{
						change = true;
						gray_aux.setPix(i, j, true);
						gray_reco.setPix(i-1, j, left);
						gray_reco.setPix(i, j, center);
						gray_reco.setPix(i+1, j, right);
						gray_reco.setPix(i, j-1, up);
						gray_reco.setPix(i, j+1, down);
					}
				}
			}
		} while(change != false);	
		
		return gray_reco;
	}
	
	
	/**
	 * Set of operation in order to detect motion
	 * @param gray ImagePix in which objects have to be found
	 */
	public static int[] motionTest(BooleanImage gray)
	{
		System.out.println("engine.motionDetection : motionTest");
		int width = gray.getWidth();
		int height = gray.getHeight();
		
		BooleanImage gray_dil = new BooleanImage(width, height);
		
		gray_dil = dilatation(gray);
		return labelling(gray_dil);
	}
	
	
	/**
	 * Detection of the position of the different objects
	 * @param gray ImagePix in which objects have to be found
	 */
	public static int[] labelling(BooleanImage gray)
	{
		System.out.println("engine.motionDetection : labelling");
		int width = gray.getWidth();
		int height = gray.getHeight();
		
		int nbMaxObjects = 500; //Maximal number of detected objects
		int[] pTabLabel = new int[nbMaxObjects];
		Arrays.fill(pTabLabel, 0);
		
		int newLabel = 1;
		int nbLabel = 0;
		
		CoordLabel[] pCoord = new CoordLabel[50];
		Arrays.fill(pCoord, new CoordLabel());
		
		ImagePix<Integer> gray_label = new ImagePix<Integer>(width, height, 0);	
		
		//to return in the end : 
		int[] grav_coord = new int[2];
		grav_coord[0] = -1;
		grav_coord[1] = -1;
		
		//Treatment
		for(int j=1; j<height-1; j++)
		{
			for(int i=1; i<width-1; i++)
			{
				if(gray.getPix(i, j))
				{
					if(gray.getPix(i-1, j) == gray.getPix(i, j-1))
					{
						if(!gray.getPix(i-1, j))
						{		
							if(gray.getPix(i-1, j-1))
							{
								gray_label.setPix(i, j,
										gray_label.getPix(i-1, j-1));
							}
							else
							{
								gray_label.setPix(i, j, newLabel);								
								pTabLabel[newLabel] = 1;			
								newLabel++;
							}
						}
						else
						{
							if(gray_label.getPix(i, j-1)< gray_label.getPix(i-1, j))
							{
								gray_label.setPix(i, j, gray_label.getPix(i, j-1));
								pTabLabel[gray_label.getPix(i-1, j)] = gray_label.getPix(i, j-1);
								gray_label.setPix(i-1, j, gray_label.getPix(i, j-1));							
							}
							else
							{
								gray_label.setPix(i, j, gray_label.getPix(i-1, j));
								pTabLabel[gray_label.getPix(i, j-1)] = gray_label.getPix(i-1, j);
								gray_label.setPix(i, j-1, gray_label.getPix(i-1, j));							
							}
						}
					}
					else if(gray.getPix(i, j-1) || gray.getPix(i-1, j))
					{
						if(gray.getPix(i, j-1))
						{
							gray_label.setPix(i, j, (gray_label.getPix(i, j-1)));						
						}
						else
						{
							gray_label.setPix(i, j, (gray_label.getPix(i-1, j)));
						}
					}
				}
			}
		}
		
		//Equivalent chart update
		for(int k=newLabel; k>0; k--)
		{
			if((pTabLabel[k] != 0) && (pTabLabel[k] != k))
			{
				for(int j=1; j<height-1; j++)
				{
					for(int i=1; i<width-1; i++)
					{
						if(gray_label.getPix(i, j) == k)
						{
							gray_label.setPix(i, j, pTabLabel[k]);							
						}
					}
				}
				pTabLabel[k] = 0;
				nbLabel--;
			}		
		}
		
		//Calculation of labels quantity
		nbLabel += newLabel - 1;
		
		int k = 0;
		//Calculation of labels quantity
		//Initialization of labels
		for(int e=0; e<nbMaxObjects; e++)
		{
			if (pTabLabel[e] != 0) 
			{
				pCoord[k] = new CoordLabel();
				pCoord[k].setLabel(e); 
				k++;
			}
		}
				
		
		//Initialization of envelopes coordinates
		for(int i=0; i<nbLabel; i++)
		{
			pCoord[i].setXMin(0);
			pCoord[i].setXMax(0);
			pCoord[i].setYMin(0);
			pCoord[i].setYMax(0);
		}
		
	
		int temp = 0;
		int save = 0;
		//Memorization of envelopes coordinates
		for(int j=1; j<height-1; j++)
		{
			for(int i=1; i<width-1; i++)
			{
				for(int e=0; e<nbLabel; e++)
				{
					if(gray_label.getPix(i, j)== pCoord[e].getLabel())
					{
						//xMin
						if ((pCoord[e].getXMin() > j) || (pCoord[e].getXMin() == 0))
							pCoord[e].setXMin(j - 1);
						
						//xMax
						else if ((j > pCoord[e].getXMax()) || (pCoord[e].getXMax() == width)) 			
							pCoord[e].setXMax(j + 1);
						
						//yMin
						if ((pCoord[e].getYMin() > i) || (pCoord[e].getYMin() == 0))	
							pCoord[e].setYMin(i - 1);
						
						//yMax
						else if ((i > pCoord[e].getYMax()) || (pCoord[e].getYMax() == width)) 				
							pCoord[e].setYMax(i + 1);	
					}
					
					//The biggest object will be the hand
					temp = (pCoord[e].getXMax() - pCoord[e].getXMin()) * (pCoord[e].getYMax() - pCoord[e].getYMin());
					
					
					if(temp > save)
					{
						save = temp;
						grav_coord[0] = pCoord[e].getXMin() + (pCoord[e].getXMax() - pCoord[e].getXMin())/2;
						grav_coord[1] = pCoord[e].getYMin() + (pCoord[e].getYMax() - pCoord[e].getYMin())/2;
					}
				}				
			}
		}	
		System.out.println("engine.motionDetection : labelling - over");
		return grav_coord;
		
	}
	
}
